using NHibernate;
using NHibernate.Type;
using TelerikMvcGridCustomBindingHelper.NHibernate.Aggregates;
using Expression = System.Linq.Expressions.Expression;

namespace TelerikMvcGridCustomBindingHelper.NHibernate.Projections
{
    public class ExpressionProjectionHelper
    {
        private readonly IQueryOver _queryOver;
        private readonly Expression _expression;
        private readonly IType _returnType;
        private const string ResultAlias = "expressionProjectionHelperResult";

        public ExpressionProjectionHelper(IQueryOver queryOver, Expression expression, IType returnType)
        {
            _queryOver = queryOver;
            _expression = expression;
            _returnType = returnType ?? NHibernateUtil.Decimal;
        }

        private IFutureValue<TResult> BuildProjection<TResult>(string aggregateMethod)
        {
            string sqlString;
            var expressionSql = _expression.ToSqlString();

            if (aggregateMethod.Equals("count"))
            {
                _queryOver.RootCriteria.Add(new BetterSqlCriterion(expressionSql));

                sqlString = string.Format("count(*) as {0}", ResultAlias);
            }
            else
            {
                sqlString = string.Format("{0}({1})", aggregateMethod, expressionSql);
            }

            var projection = new BetterSqlProjection(sqlString, ResultAlias, _returnType);

            _queryOver.RootCriteria.SetProjection(projection);

            return _queryOver.RootCriteria.FutureValue<TResult>();
        }

        public IFutureValue<object> Count()
        {
            return Count<object>();
        }

        public IFutureValue<TResult> Count<TResult>()
        {
            return BuildProjection<TResult>("count");
        }

        public IFutureValue<object> Sum()
        {
            return Sum<object>();
        }

        public IFutureValue<TResult> Sum<TResult>()
        {
            return BuildProjection<TResult>("sum");
        }

        public IFutureValue<object> Average()
        {
            return Average<object>();
        }

        public IFutureValue<TResult> Average<TResult>()
        {
            return BuildProjection<TResult>("average");
        }

        public IFutureValue<object> Min()
        {
            return Min<object>();
        }

        public IFutureValue<TResult> Min<TResult>()
        {
            return BuildProjection<TResult>("min");
        }

        public IFutureValue<object> Max()
        {
            return Max<object>();
        }

        public IFutureValue<TResult> Max<TResult>()
        {
            return BuildProjection<TResult>("max");
        }
    }
}